import sumolib
import traci
import traci.constants as tc

DEFAULT_LC = 0b011001010101
FIX_LC = 0b0000000000


# ==================================================================================================
# -- sumo simulation -------------------------------------------------------------------------------
# ==================================================================================================

class SumoSimulation(object):
    """
    SumoSimulation is responsible for the management of the sumo simulation.
    """

    def __init__(self, cfg_file, sumo_gui=True):
        if sumo_gui is True:
            sumo_binary = sumolib.checkBinary('sumo-gui')
        else:
            sumo_binary = sumolib.checkBinary('sumo')

        traci.start([sumo_binary,
                     '--configuration-file', cfg_file,
                     '--step-length', '0.1',
                     "--collision.action", "none",
                     "--start",
                     "--duration-log.statistics",
                     "--scale", '1',
                     ])

        # Structures to keep track of the spawned and destroyed vehicles at each time step.
        self._depart_vehicles = set()
        self._arrive_vehicles = set()

    def get_depart_vehicles(self):
        return self._depart_vehicles

    def get_arrive_vehicles(self):
        return self._arrive_vehicles

    @staticmethod
    def get_trafficlights():
        return traci.trafficlight.getIDList()

    @staticmethod
    def subscribe(vehicle_id):
        """
        Subscribe the given actor to the following variables:

            * Type.
            * Length
            * Position3D (i.e., x, y, z).
            * Speed.
        """
        traci.vehicle.subscribe(vehicle_id, [
            traci.constants.VAR_TYPE,
            traci.constants.VAR_LENGTH,
            traci.constants.VAR_SPEED,
            traci.constants.VAR_POSITION3D,
            traci.constants.VAR_LANE_ID,
            traci.constants.VAR_LANE_INDEX,
            traci.constants.VAR_LANEPOSITION,
            traci.constants.VAR_ROAD_ID,
            traci.constants.VAR_ACCELERATION,
            traci.constants.VAR_ANGLE
        ])

    @staticmethod
    def unsubscribe(vehicle_id):
        """
        Unsubscribe the given actor from receiving updated information each step.
        """
        traci.vehicle.unsubscribe(vehicle_id)

    @staticmethod
    def get_vehicle_type(vehicle_id):
        """
        Gets the type of the vehicle.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_TYPE]

    @staticmethod
    def get_vehicle_length(vehicle_id):
        """
        Gets the length of the vehicle.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_LENGTH]

    @staticmethod
    def get_vehicle_speed(vehicle_id):
        """
        Gets the speed of the vehicle.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_SPEED]

    @staticmethod
    def get_vehicle_position(vehicle_id):
        """
        Gets the 3D position of the vehicle.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        position = results[traci.constants.VAR_POSITION3D]
        return position

    @staticmethod
    def get_vehicle_rotation(vehicle_id):
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        position = results[traci.constants.VAR_ANGLE]
        return position

    @staticmethod
    def get_vehicle_lane_id(vehicle_id):
        """
        Gets the lane ID where the vehicle is currently located.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_LANE_ID]

    @staticmethod
    def get_vehicle_lane_index(vehicle_id):
        """
        Gets the lane ID where the vehicle is currently located.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_LANE_INDEX]

    @staticmethod
    def get_vehicle_lane_position(vehicle_id):
        """
        Gets the vehicle's position on the current lane.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_LANEPOSITION]

    @staticmethod
    def get_vehicle_road_id(vehicle_id):
        """
        Gets the vehicle's position on the current lane.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_ROAD_ID]

    @staticmethod
    def get_vehicle_acceleration(vehicle_id):
        """
        Gets the vehicle's position on the current lane.
        """
        results = traci.vehicle.getSubscriptionResults(vehicle_id)
        return results[traci.constants.VAR_ACCELERATION]

    @staticmethod
    def set_vehicle_color(vehicle_id, color):
        traci.vehicle.setColor(vehicle_id, color)

    def tick(self):
        """
        Tick to sumo simulation.
        """
        traci.simulationStep()

        # Update data structures for the current frame.
        self._depart_vehicles = set(traci.simulation.getDepartedIDList())
        for depart_vehicle in self._depart_vehicles:
            self.subscribe(depart_vehicle)

        self._arrive_vehicles = set(traci.simulation.getArrivedIDList())

    @staticmethod
    def close():
        """
        Closes traci client.
        """
        traci.close()

    @staticmethod
    def set_fixed_lane(vid):
        traci.vehicle.setLaneChangeMode(vid, FIX_LC)

    @staticmethod
    def get_vehicle_allowed_speed(vid):
        return traci.vehicle.getAllowedSpeed(vid)

    @staticmethod
    def get_vehicle_max_speed(vid):
        return traci.vehicle.getMaxSpeed(vid)

    @staticmethod
    def get_lane_max_speed(lid):
        return traci.lane.getMaxSpeed(lid)

    @staticmethod
    def change_lane(vid, lane):
        """
        Let a vehicle change lane without respecting any safety distance
        :param vid: vehicle id
        :param lane: lane index
        :type vid: str
        :type lane: int
        """
        traci.vehicle.setLaneChangeMode(vid, FIX_LC)
        traci.vehicle.changeLane(vid, lane, 10)

    @staticmethod
    def set_speed_factor(vehicle_id, speed_factor):
        traci.vehicle.setSpeedFactor(vehicle_id, speed_factor)

    @staticmethod
    def get_vehicle_min_gap(vehicle_id):
        return traci.vehicle.getMinGap(vehicle_id)

    @staticmethod
    def get_vehicle_route(vehicle_id):
        return traci.vehicle.getRoute(vehicle_id)

    @staticmethod
    def get_driving_distance(vehicle_id, next_edge, pos):
        return traci.vehicle.getDrivingDistance(vehicle_id, next_edge, pos)

    @staticmethod
    def get_lane_vehicles(lane_id):
        return traci.lane.getLastStepVehicleIDs(lane_id)

    @staticmethod
    def get_trafficlight_lanes(trafficlight_id):
        return traci.trafficlight.getControlledLanes(trafficlight_id)

    @staticmethod
    def get_trafficlight_status(trafficlight_id):
        return traci.trafficlight.getRedYellowGreenState(trafficlight_id)

    @staticmethod
    def get_trafficlight_remaining_duration(trafficlight_id):
        return traci.trafficlight.getNextSwitch(trafficlight_id) - traci.simulation.getTime()

    @staticmethod
    def get_lanes():
        return traci.lane.getIDList()

    @staticmethod
    def get_lane_vehicle_number(lane_id):
        return traci.lane.getLastStepVehicleNumber(lane_id)
